/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.forces.gravity.potential;

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;

import ore.io.DataLoader;
import ore.errors.OrekitException;

/**
 * A Gravitational Potential Coefficients file reader used by {@link
 * PotentialReaderFactory} for the various coefficients models
 * implemented by subclasses.
 *
 * @see PotentialReaderFactory
 * 
 * @author Fabien Maussion
 */
public abstract class PotentialCoefficientsReader
    extends Object
    implements DataLoader, PotentialCoefficientsProvider
{

    private static final String TOO_LARGE_DEGREE =
        "too large degree (n = {0}, potential maximal degree is {1})";

    private static final String TOO_LARGE_ORDER =
        "too large order (m = {0}, potential maximal order is {1})";



    protected boolean readCompleted;
    /** 
     * Central body reference radius.
     */
    protected double ae;
    /** 
     * Central body attraction coefficient.
     */
    protected double mu;
    /** 
     * Fully normalized zonal coefficients array.
     */
    protected double[] normalizedJ;
    /** 
     * Fully normalized tesseral-sectorial coefficients matrix.
     */
    protected double[][] normalizedC;
    /** 
     * Fully normalized tesseral-sectorial coefficients matrix.
     */
    protected double[][] normalizedS;
    /**
     * Denormalized zonal coefficients array.
     */
    private double[] denormalizedJ;
    /**
     * Denormalized tesseral-sectorial coefficients matrix.
     */
    private double[][] denormalizedC;
    /**
     * Denormalized tesseral-sectorial coefficients matrix.
     */
    private double[][] denormalizedS;

    private final String supportedNames;


    protected PotentialCoefficientsReader(String supportedNames) {
        super();
        this.supportedNames = supportedNames;
        this.ae = Double.NaN;
        this.mu = Double.NaN;
    }


    public String getSupportedNames() {
        return this.supportedNames;
    }
    public boolean stillAcceptsData() {
        return (!this.readCompleted);
    }
    public abstract void loadData(InputStream input, String name)
        throws IOException, ParseException, OrekitException;

    public double[] getJ(final boolean normalized, final int n)
        throws OrekitException
    {
        if (n >= this.normalizedC.length) {
            throw new OrekitException(TOO_LARGE_DEGREE, n, normalizedC.length - 1);
        }
        else {
            final double[] completeJ = normalized ? this.getNormalizedJ() : this.getDenormalizedJ();

            final double[] result = new double[n + 1];

            System.arraycopy(completeJ, 0, result, 0, n + 1);

            return result;
        }
    }
    public double[][] getC(int n, int m, boolean normalized)
        throws OrekitException
    {
        return Head(n, m, normalized ? this.getNormalizedC() : this.getDenormalizedC());
    }
    public double[][] getS(int n, int m, boolean normalized)
        throws OrekitException
    {
        return Head(n, m, normalized ? this.getNormalizedS() : this.getDenormalizedS());
    }
    public double getMu() {
        return this.mu;
    }
    public double getAe() {
        return this.ae;
    }
    /**
     * @return J, the fully normalized zonal coefficients matrix
     */
    private double[] getNormalizedJ() {
        if (this.normalizedJ == null) {
            this.normalizedJ = new double[this.normalizedC.length];
            for (int i = 0; i < this.normalizedC.length; i++) {
                this.normalizedJ[i] = -this.normalizedC[i][0];
            }
        }
        return this.normalizedJ;
    }
    /** 
     * @return C, thef fully normalized tesseral-sectorial and zonal
     * coefficients matrix
     */
    private double[][] getNormalizedC() {
        return this.normalizedC;
    }
    /** 
     * @return S, the fully normalized tesseral-sectorial coefficients
     * matrix
     */
    private double[][] getNormalizedS() {
        return this.normalizedS;
    }
    /** 
     * @return J, the denormalized zonal coefficients array.
     */
    private double[] getDenormalizedJ() {
        if (this.denormalizedJ == null) {
            final double[][] uC = this.getDenormalizedC();
            this.denormalizedJ = new double[uC.length];
            for (int i = 0; i < uC.length; i++) {
                this.denormalizedJ[i] = -uC[i][0];
            }
        }
        return this.denormalizedJ;
    }
    /** 
     * @return C, the denormalized tesseral-sectorial and zonal
     * coefficients matrix
     */
    private double[][] getDenormalizedC() {
        if (this.denormalizedC == null) {
            this.denormalizedC = Denormalize(this.normalizedC);
        }
        return this.denormalizedC;
    }
    /** 
     * @return S the denormalized tesseral-sectorial coefficients matrix
     */
    private double[][] getDenormalizedS() {
        if (this.denormalizedS == null) {
            this.denormalizedS = Denormalize(normalizedS);
        }
        return this.denormalizedS;
    }
    /**
     * Apply denormalization
     */
    private static double[][] Denormalize(final double[][] normalized) {
        /*
         * Allocate a triangular array
         */
        final double[][] denormalized = new double[normalized.length][];
        denormalized[0] = new double[] {
            normalized[0][0]
        };
        /*
         * Initialization
         */
        double factN = 1.0;
        double mfactNMinusM = 1.0;
        double mfactNPlusM = 1.0;
        /*
         * Denormalize the coefficients
         */
        for (int n = 1; n < normalized.length; n++) {
            final double[] uRow = new double[n + 1];
            final double[] nRow = normalized[n];
            final double coeffN = 2.0 * (2 * n + 1);
            factN *= n;
            mfactNMinusM = factN;
            mfactNPlusM = factN;
            uRow[0] = Math.sqrt(2 * n + 1) * normalized[n][0];
            for (int m = 1; m < nRow.length; m++) {
                mfactNPlusM  *= n + m;
                mfactNMinusM /= n - m + 1;
                uRow[m] = Math.sqrt((coeffN * mfactNMinusM) / mfactNPlusM) * nRow[m];
            }
            denormalized[n] = uRow;
        }

        return denormalized;

    }
    /**
     * Copy degree 'n' and order 'm' from 'complete', for 'n' and 'm'
     * less than the degree and order of 'complete'.
     */
    private static double[][] Head(int n, int m, double[][] complete)
        throws OrekitException
    {
        if (n >= complete.length) {
            throw new OrekitException(TOO_LARGE_DEGREE, n, complete.length - 1);
        }
        else if (m >= complete[complete.length - 1].length) {
            throw new OrekitException(TOO_LARGE_ORDER, m, complete[complete.length - 1].length - 1);
        }
        else {

            double[][] result = new double[n + 1][];
            for (int i = 0; i <= n; i++) {
                double[] ri = new double[Math.min(i, m) + 1];
                System.arraycopy(complete[i], 0, ri, 0, ri.length);
                result[i] = ri;
            }

            return result;
        }
    }

}
